from src.connector import get_client
from bson import json_util, ObjectId
from src.utils import get_date, add_timestamp
from src.error_handler import *
import json
from app import create_app

client = get_client()
app = create_app('development')

def get_user_length():
    user_length = client.db.user.find().count()
    return user_length

def check_if_user_exist(obj_id):
    res = client.db.user.find({"_id" : ObjectId(obj_id)}).count()
    if res == 1:
        return True
    else:
        return False

def check_if_username_exist(username, obj_id = None):
    res = 0
    if obj_id:
        res = client.db.user.find({"username" : username, "_id" : { "$ne": ObjectId(obj_id)}}).count()
    else:
        res = client.db.user.find({"username" : username}).count()
    return res

def check_tags_exist_by_id(tag):
    app.logger.info("----invoke check tags method-----")
    res = client.db.tags.find_one({"_id" : ObjectId(tag["key"])}, {"_id" : 0 })
    app.logger.debug(res)
    if res:
        return res["key"]
    else:
        return False

def check_tags_exist_by_name(tag):
    app.logger.info("----invoke check tags method-----")
    res = client.db.tags.find_one({"value" : tag["value"]})

    if res:
        app.logger.debug("Found exist tag: ")
        app.logger.debug(res)
        return str(res["_id"])
    else:
        return False

def db_get_all_tags():
    app.logger.info("----invoke all tags----")
    tags = client.db.tags.find({},{"_id": 0})
    data = json.loads(json_util.dumps(tags))
    return data

def insert_tag(tag):
    app.logger.info("----invoke insert tags method-----")
    res = client.db.tags.insert(tag)
    app.logger.debug("insert TAG")
    app.logger.debug(res)
    return res

def restructure_tags(degrees):
    app.logger.info("----invoke restructure_tags method-----")
    for index_degree,ele in enumerate(degrees):
        for index_tag,tag in enumerate(ele["possible_tags"]):
            if tag.get("key") == "" or tag.get("key") == None \
            or type(tag.get("key")) is int :
                if check_tags_exist_by_name(tag) == False:
                    tag["key"] = str(insert_tag(tag))
                else:
                    tag["key"] = check_tags_exist_by_name(tag)
    app.logger.debug(degrees)
    return degrees

def restructure_tags_for_dream(tags):
    app.logger.info("----invoke restructure_tags_for_dream method-----")
    for index_tag,tag in enumerate(tags):
        if tag.get("key") == "" or tag.get("key") == None \
        or type(tag.get("key")) is int :
            if check_tags_exist_by_name(tag) == False:
                tag["key"] = str(insert_tag(tag))
            else:
                tag["key"] = check_tags_exist_by_name(tag)
    return tags

def db_get_all_users():
    users = client.db.user.find()
    data = json.loads(json_util.dumps(users))
    return data

# def check_x_token(headers):
#     token = None
#     return headers.get("X-Token", None)

def db_login(dict_data):
    """ Match user and password
    All activated users are allowed to login, of course they must know the correct password..

    @param dict_data: {"username" : username, "secret": sha256 encoded password}
    @return: return { token: _id, code : 20000 } if no exeception.
             return { message: error message, code: 50001 } otherwise.
    """
    username = dict_data["username"]
    password = dict_data["secret"]
    if username and password:
        app.logger.debug("POST DATA: " + username + " | " + password)

        user = client.db.user.find_one({"username" : username, "secret" : password, "is_activate" : True})
        if user:
            app.logger.info(user["username"] + " login successfully!")
            user["id"] = str(user["_id"])
            client.db.user.update_one({"_id" : user["_id"]},{"$set":{"online" : True}})
            app.logger.info("user : " + username + " set online = true.")
            res = {"code" : 20000, "data":{"token" : user["id"]}}
            return res
        else:
            app.logger.info("Login error: " + username + "|" + password)
            return {"message" : "Login or Password Error", "code" : 50001}
    else:
        return {"code": 50001, "message" : "Login or Password Error"}

def db_get_user_info(token):
    """
    To fetch user profile by X-Token in header

    @param token: the token is saved at request header
    @return: { code:20000, data: user profile } if no exception
             { code: 50002, message: error message } if token is incorrect
    """
    app.logger.debug("Get user info:" + token)

    obj_id = ObjectId(token)
    user = client.db.user.find_one({"_id" : obj_id}, {"secret":0})

    if user is not None:
        user["_id"] = str(user["_id"])
        res = {"message" : "OK", "code" : 20000, "data" : user}
        app.logger.info("----get user info successfully----")
        app.logger.debug(res)
        return res
    else:
        app.logger.info("----get user info failed----")
        return {"message" : "Get user profile failed!", "code" : 50002}


def db_logout(token):
    """
    Set user logout is equal to online = false
    @param token: user id
    @return {"code" : 20000, "message" : success } if no exception
    """
    logout = client.db.user.update_one({"_id": ObjectId(token)}, {"$set": {"online" : False}})
    if logout.modified_count == 1:
        return {"message" : "Log out successfully!", "code" : 20000}
    else:
        return {"message" : "Log out successfully, but someting is anormal..", "code" : 20000}

def db_create_user(dict_data):
    """
    Create a new user and save to database
    @param dict_data: {"username" : username, "secret" : secret coded in sha, "email" : user email }
    @return response: {"code" : 20000, "showMsg" : if front end is supposed to show message, "message": success msg} if no exception
                      {"code" : 50004, "message" : error message} if exception
    """
    if dict_data["email"] and dict_data["username"] and dict_data["secret"]:
        if check_if_username_exist(dict_data["username"], None) != 0:
            return {"message" : "Username exist! Please choose other username!", "code" : 50004}
        else:
            dict_data["create_date"] = get_date()
            dict_data["is_activate"] = True
            dict_data["language"] = "zn"
            dict_data["friends"] = []
            dict_data["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80"
            dict_data["16perso"] = ""
            dict_data["dream"] = ""
            dict_data["online"] = False
            dict_data["role"] = "user"
            dict_data["sex"] = "M"
            dict_data["age"] = 18
            id = client.db.user.insert(dict_data)
            if id:
                app.logger.info("Create user : " + dict_data["username"] + " done.")
                return {"showMsg" : True, "message" : "Create user successfully!", "code" : 20000}
            else:
                return {"message" : "Create user failure due to server internal error.", "code" : 50004}
    else:
        return { "message" : "Create user failure, ressource missing.", "code" : 50004}

def db_update_profile(headers, dict_data):
    """
    Update user profile, we need to check if the new username has conflit with others.
    @param headers: header of request, to get X-Token
    @param dict_data: post data, like {"username": username, "sex": sex, "age": xx, etc...}
    @return response: {"showMsg" : if you want to info user response, "code": 20000, "message"} if no exception
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}
    if check_if_username_exist(dict_data["username"], obj_id) > 0:
        app.logger.debug("Username exist, update failed.")
        return {"message" : "Username exist! Please choose other username! ", "code" : 50005, "showMsg" : True}
    if check_if_user_exist(obj_id):
        # update tuple
        update = client.db.user.update_one({"_id": ObjectId(obj_id)}, {"$set" : dict_data})
        app.logger.debug(update)
        if update.modified_count == 1:
            return {"showMsg" : True, "message" : " Update profile successfully.", "code" : 20000}
        else:
            return {"message" : "Update failed, maybe you change nothing?", "code": 50005}
    else:
        return {"message" : "Update failed, user unknown!", "code": 50005}

def db_change_password(headers, dict_data):
    """
    Allow user change password
    @param headers: headers of request to get X-Token
    @param dict_data:{secret : new password}
    @return response: {code: 20000, showMsg: xx, message: "success"} if no exception
                      {code: 50006, showMsg: xx, message: "error explication"} if update failed

    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}
    user = client.db.user.find_one({"_id": ObjectId(obj_id), "secret" : dict_data["secret"]})
    if user:
        update_secret = client.db.user.update_one({"_id": ObjectId(obj_id)}, {"$set": {"secret": dict_data["new_secret"]}})
        if update_secret.modified_count == 1:
            return {"showMsg" : True, "message" : "Update password successfully.", "code" : 20000}
        else :
            return {"showMsg" : True, "message" : UPDATE_PASS_NO_CHANGE_ERROR["msg"], "code" : UPDATE_PASS_NO_CHANGE_ERROR["code"], "msgType": 'error'}
    else:
        return {"showMsg" : True, "message" : UPDATE_PASS_ERROR["msg"], "code" : UPDATE_PASS_ERROR["code"], "msgType" : "error"}



def db_get_classification(headers):
    """
    Fetch classification of X-Token, each user can set just one classification , so we can fetch it by user: _id
    @param headers: headers of request to get X-Token
    @return response: {code: 20000, showMsg: xx, message: "msg", data: classification fetched} if user's classification exist
                      {code: 20000, showMsg: xx, message: "default", data: classification fetched} if user has no classification, return a default classfication
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}
    app.logger.debug("get classification get id : " + obj_id)
    classification = client.db.classifications.find_one({"uid": ObjectId(obj_id)}, {"uid" : 0})
    if classification:
        for ele in classification["degree"]:
            for tag in ele["possible_tags"]:
                if tag.get("_id"):
                    tag["_id"] = str(tag["_id"])
        classification["_id"] = str(classification["_id"])
        return {"code" : 20000, "showMsg" : False, "message" : "FETCH CLASSIFICATION OK", "data" : classification}
    else:
        classification = client.db.classifications.find_one({"title" : "default"}, {"_id" : 0, "uid" : 0})
        return {"code" : 20000, "showMsg" : False, "message" : "GET DEFAULT CLASSIFICATION OK", "data" : classification}

def check_user_classification(obj_id):
    cls = client.db.classifications.find_one({"uid" : ObjectId(obj_id)})
    if cls:
        return True
    return False

def db_update_classification(headers, dict_data):
    """
    Update or create user's classification of dream
    @param headers: headers of request to get X-Token
    @param dict_data: {a full object of classification}
    @return response : {code : 20000, "showMsg": true or false, "msgType": type, message: msg} if no exception
                      {code: 51001, "showMsg": true or false, "msgType": type, "message": msg} if exception
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    dict_data["degree"] = restructure_tags(dict_data["degree"])
    if dict_data["title"] != "" and check_user_classification(obj_id):
        app.logger.debug("user has already a classification, update it!")
        dict_data = add_timestamp(dict_data, "UPDATE")
        update_cls = client.db.classifications.update_one({"uid": ObjectId(obj_id)}, {"$set" : dict_data})
        if update_cls.modified_count != 1:
            return {"message" : UPDATE_CLS_NO_CHANGE_ERROR["msg"], "code" : UPDATE_CLS_NO_CHANGE_ERROR["code"], "showMsg": True, "msgType": "error"}
    elif check_user_classification(obj_id) == False:
        app.logger.debug("user has no classification, insert it!")
        dict_data["uid"] = ObjectId(obj_id)
        dict_data = add_timestamp(dict_data, "CREATE")
        new_cls = client.db.classifications.insert(dict_data)
        app.logger.debug(new_cls)
        if new_cls == None:
            return {"message" : INSERT_CLS_FAILURE_ERROR["msg"], "code" : INSERT_CLS_FAILURE_ERROR["code"], "showMsg": True, "msgType": "error"}
    else:
        app.logger.debug("update classification unknow reason...")
        return {"message" : UPDATE_CLS_NO_CHANGE_ERROR["msg"], "code" : UPDATE_CLS_NO_CHANGE_ERROR["code"], "showMsg": True, "msgType": "error"}

    return {"message" : "update classification OK", "code" : 20000, "showMsg" : True, "msgType" : "success"}

def db_get_brief_dream(headers):
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    ignore_fields = {"_id" : 0, "content": 0, "uid":0, "create_time": 0, \
                 "is_deleted": 0, "last_update_time" : 0 }
    dreams = client.db.dreams.find({"uid" : ObjectId(obj_id), "is_deleted" : False}, ignore_fields)
    if dreams:
        app.logger.debug("get dreams of uid : " + obj_id)
        data = json.loads(json_util.dumps(dreams))
        for dream in data:
            for tag in dream["tags"]:
                if tag.get("_id"):
                    tag["_id"] = str(tag["_id"])
        return {"code" : 20000, "showMsg" : False, "message" : "OK", "data" : data}
    else:
        return {"code" : 20000, "showMsg" : False, "message" : "USER HAS NO DREAM"}

def db_get_dreams(headers):
    """
    Load user's non-deleted dream articles by X-Token
    @param headers: headers of request to get X-Token
    @return response:{code : 20000, "showMsg": true or false, "msgType": type, message: msg} if no exception
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    dreams = client.db.dreams.find({"uid" : ObjectId(obj_id), "is_deleted" : False}, { "uid" : 0 })
    if dreams:
        app.logger.debug("get dreams of uid : " + obj_id)
        data = json.loads(json_util.dumps(dreams))
        for dream in data:
            dream["_id"] = str(dream["_id"]["$oid"])
            dream["dream_date"] = dream["dream_date"][0:10]
            for tag in dream["tags"]:
                if tag.get("_id"):
                    tag["_id"] = str(tag["_id"])
        return {"code" : 20000, "showMsg" : False, "message" : "OK", "data" : data}
    else :
        return {"code" : 20000, "showMsg" : False, "message" : "USER HAS NO DREAM"}

def db_get_dream_by_id(headers, id):
    """
    Find a dream by dream._id, which is used when editing a dream or reading a dream
    @param headers: headers of request to get X-Token
    @param id: id of target dream
    @return: response: {code: 20000, "message": msg, showMsg: true or false, "data": target dream object}
                       {code: 52001, "message": errorMsg, showMsg: true or false}
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    dream = client.db.dreams.find_one({"uid" : ObjectId(obj_id), "is_deleted" : False, "_id" : ObjectId(id)}, { "uid" : 0 })
    if dream:
        for ele in dream["tags"]:
            if ele.get("_id"):
                ele["_id"] = str(ele["_id"])
        dream["_id"] = str(dream["_id"])
        dream["degree_info"]["classification_id"] = str(dream["degree_info"]["classification_id"])
        return {"code" : 20000, "showMsg": False, "message": "Fetch DREAM OK", "data": dream}
    else :
        return {"code": SEARCH_DREAM_BY_ID_ERROR["code"], "showMsg" : True, "message" : SEARCH_DREAM_BY_ID_ERROR["msg"]}

def db_update_dream_by_id(headers, dict_data):
    """
    Update a dream by dream._id, which is used when editing a dream
    @param headers: headers of request to get X-Token
    @param id: id of target dream
    @return: response: {code: 20000, "message": msg, showMsg: true or false, "data": target dream object}
                       {code: 52001, "message": errorMsg, showMsg: true or false}
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    dict_data = add_timestamp(dict_data, "UPDATE")
    dict_data["tags"] = restructure_tags_for_dream(dict_data["tags"])
    dream_id = dict_data.pop("_id")
    dream_up = client.db.dreams.update_one({"uid" : ObjectId(obj_id), "_id": ObjectId(dream_id)}, {"$set": dict_data})
    if dream_up.modified_count == 1:
        return {"code" : 20000, "showMsg": True, "message": "Update OK"}
    else:
        return {"code" : UPDATE_DREAM_BY_ID_ERROR["code"], "message" : UPDATE_DREAM_BY_ID_ERROR["msg"], "showMsg": True, "msgType": "error"}

def db_create_dream(headers, dict_data):
    """
    Create new dream and return the generated id
    @param headers: headers of request to get X-Token
    @return: response: {code: 20000, "message": msg, showMsg: true or false, "id": target dream id}
    """
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    dict_data = add_timestamp(dict_data, "CREATE")
    dict_data["tags"] = restructure_tags_for_dream(dict_data["tags"])
    dict_data["uid"] = ObjectId(obj_id)
    dict_data["is_deleted"] = False
    new_dream = client.db.dreams.insert(dict_data)
    app.logger.debug(new_dream)
    if new_dream:
        new_dream_id = str(new_dream)
        return {"code": 20000, "message": "Create dream successfully!", "showMsg": True, "msgType": "success", "data": { "_id" : new_dream_id }}
    else:
        return {"code": CREATE_DREAM_ERROR["code"], "message": CREATE_DREAM_ERROR["msg"], "showMsg": True, "typeMsg": "error"}


def db_delete_dream_by_id(headers, dict_data):
    obj_id = headers.get("X-Token", None)
    if obj_id == None:
        return {"message" : UNKNOW_TOKEN_ERROR["msg"], "code": UNKNOW_TOKEN_ERROR["code"], "showMsg": True, "msgType": "error"}

    # deleted = client.db.dreams.delete_one({ "_id" : ObjectId(dict_data["_id"]), "uid" : ObjectId(obj_id) })
    deleted = client.db.dreams.update_one({ "_id": ObjectId(dict_data["_id"]), "uid": ObjectId(obj_id), "is_deleted": False }, { "$set" : { "is_deleted" : True }})
    if deleted.modified_count == 1:
        return {"message" : "Delete dream successfully!", "code" : 20000, "showMsg": True, "msgType": "success"}
    else:
        return {"message": DELETE_DREAM_ERROR["msg"], "code": DELETE_DREAM_ERROR["code"], "showMsg": True, "msgType": "error"}
